In [3]:
# STAT 415/615 Regression (M. Baron)
# Python Lab 7: Matrix Operations and Multivariate Linear Regression
# Define a matrix by entering its elements manually.
import numpy as np
B = np.array([
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]
])
B
Out[3]:
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
In [4]:
# B**2 is an element-wise operation
B**2
Out[4]:
array([[ 1, 16, 49],
[ 4, 25, 64],
[ 9, 36, 81]])
In [5]:
# Matrix multiplication, B^2 = B * B
B @ B
Out[5]:
array([[ 30, 66, 102],
[ 36, 81, 126],
[ 42, 96, 150]])
In [6]:
# Transposed matrix
B.T
Out[6]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [7]:
# Joining two matrices side by side (as columns)
np.column_stack((B, B))
Out[7]:
array([[1, 4, 7, 1, 4, 7],
[2, 5, 8, 2, 5, 8],
[3, 6, 9, 3, 6, 9]])
In [8]:
# Joining two matrices below each other (as rows)
np.row_stack((B, B))
Out[8]:
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9],
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
In [9]:
# Sub-matrix, a part of matrix B
B[0:2, 0:3]
Out[9]:
array([[1, 4, 7],
[2, 5, 8]])
In [10]:
# Inverting matrices is available in NumPy
np.linalg.inv(B)
--------------------------------------------------------------------------- LinAlgError Traceback (most recent call last) Cell In[10], line 3 1 # Inverting matrices is available in NumPy ----> 3 np.linalg.inv(B) File ~\AppData\Local\anaconda3\Lib\site-packages\numpy\linalg\linalg.py:561, in inv(a) 559 signature = 'D->D' if isComplexType(t) else 'd->d' 560 extobj = get_linalg_error_extobj(_raise_linalgerror_singular) --> 561 ainv = _umath_linalg.inv(a, signature=signature, extobj=extobj) 562 return wrap(ainv.astype(result_t, copy=False)) File ~\AppData\Local\anaconda3\Lib\site-packages\numpy\linalg\linalg.py:112, in _raise_linalgerror_singular(err, flag) 111 def _raise_linalgerror_singular(err, flag): --> 112 raise LinAlgError("Singular matrix") LinAlgError: Singular matrix
In [11]:
# What happened? Singular matrix means that there is a linear dependence among columns (and among rows)
# of matrix B. Such matrices are not invertible, and they have a determinant equal to det(B) = 0.
# So, we did not make an error, except that we tried to invert a non-invertible matrix.
# Okay, let's change one element, to make sure the inverse matrix exists.
np.linalg.det(B)
B[0, 0] = 100
B
Out[11]:
array([[100, 4, 7],
[ 2, 5, 8],
[ 3, 6, 9]])
In [12]:
# We changed the matrix by adding a "ridge", and now the inverse B^(-1) exists.
np.linalg.inv(B)
Out[12]:
array([[ 0.01010101, -0.02020202, 0.01010101],
[-0.02020202, -2.95959596, 2.64646465],
[ 0.01010101, 1.97979798, -1.65656566]])
In [14]:
# Define a matrix from the "mtcars" data set and build a regression model
# that predicts miles per gallon based on the number of cylinders,
# horsepower, axel ratio, weight, and acceleration time.
import statsmodels.api as sm
mtcars = sm.datasets.get_rdataset("mtcars").data
mtcars.head()
Out[14]:
| mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| rownames | |||||||||||
| Mazda RX4 | 21.0 | 6 | 160.0 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
| Mazda RX4 Wag | 21.0 | 6 | 160.0 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
| Datsun 710 | 22.8 | 4 | 108.0 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
| Hornet 4 Drive | 21.4 | 6 | 258.0 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
| Hornet Sportabout | 18.7 | 8 | 360.0 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
In [15]:
# X-matrix of predictors
X = mtcars[["cyl", "hp", "drat", "wt", "qsec"]].values
In [20]:
X[:6]
Out[20]:
array([[ 6. , 110. , 3.9 , 2.62 , 16.46 ],
[ 6. , 110. , 3.9 , 2.875, 17.02 ],
[ 4. , 93. , 3.85 , 2.32 , 18.61 ],
[ 6. , 110. , 3.08 , 3.215, 19.44 ],
[ 8. , 175. , 3.15 , 3.44 , 17.02 ],
[ 6. , 105. , 2.76 , 3.46 , 20.22 ]])
In [21]:
# Vector of responses
Y = mtcars["mpg"].values
n = len(Y)
# We also need a vector of 1s to include the intercept
one = np.ones((n, 1))
X = np.column_stack((one, X))
# This is matrix X'X
X.T @ X
Out[21]:
array([[3.20000000e+01, 1.98000000e+02, 4.69400000e+03, 1.15090000e+02,
1.02952000e+02, 5.71160000e+02],
[1.98000000e+02, 1.32400000e+03, 3.22040000e+04, 6.91400000e+02,
6.79404000e+02, 3.47556000e+03],
[4.69400000e+03, 3.22040000e+04, 8.34278000e+05, 1.63722800e+04,
1.64717440e+04, 8.10921600e+04],
[1.15090000e+02, 6.91400000e+02, 1.63722800e+04, 4.22790700e+02,
3.58718960e+02, 2.05691400e+03],
[1.02952000e+02, 6.79404000e+02, 1.64717440e+04, 3.58718960e+02,
3.60901070e+02, 1.82809458e+03],
[5.71160000e+02, 3.47556000e+03, 8.10921600e+04, 2.05691400e+03,
1.82809458e+03, 1.02934802e+04]])
In [22]:
# Slope β = (X'X)^(-1) X'Y
slope = np.linalg.inv(X.T @ X) @ X.T @ Y
slope
Out[22]:
array([ 2.59452057e+01, -4.89672771e-01, -1.53892029e-02, 1.13077207e+00,
-3.38279350e+00, 3.50112654e-01])
In [23]:
# We can certainly get the same slopes by the usual regression command "OLS"
X_reg = sm.add_constant(
mtcars[["cyl", "hp", "drat", "wt", "qsec"]]
)
reg = sm.OLS(mtcars["mpg"], X_reg).fit()
print(reg.params)
const 25.945206 cyl -0.489673 hp -0.015389 drat 1.130772 wt -3.382794 qsec 0.350113 dtype: float64
In [24]:
# Our estimated regression equation is
# mpg = 25.95 - 0.49 cyl - 0.015 hp + 1.13 drat - 3.38 wt + 0.35 qsec + ε